﻿using System;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using Yoozoo.Managers.ResourceManagerV2.Runtime;

namespace com.yoozoo.gta.Gameplay.PVE
{
    public class ResObjectCacheManager : MonoBehaviour
    {
        public static bool isSlowLoading = false;
        public class ResCache
        {
            static int CulledLayerMask = LayerMask.NameToLayer("Culled");
            
            public int RefTimes => m_RefTimes;
            private int m_RefTimes;
            public int LoadTimes => m_LoadTimes;
            private int m_LoadTimes;
            private GameObject m_Obj;
            private int m_OriginLayerMask;
            private string m_path;
            private List<GameObject> m_Pool;
            private ResLoader resLoader;

            public ResCache(string path)
            {
                this.m_path = path;
                m_RefTimes = 0;
                m_Pool = new List<GameObject>(32);
            }

            private List<Action<GameObject>> actionList = new List<Action<GameObject>>(32);
            public bool isLoading;
            public bool canLoad => actionList.Count > 0;
            
            public void Load()
            {
                BattleDebug.LogError("开始加载 " + m_path);
                for (int i = 0; i < actionList.Count; i++)
                {
                    Pop(actionList[i]);
                }
                actionList.Clear();
            }
            
            public void Load(Action<GameObject> completeFunc)
            {
                m_RefTimes += 1;
                if (isSlowLoading)
                {
                    isLoading = true;
                    actionList.Add((o) =>
                    {
                        if (isLoading)
                        {
                            BattleDebug.LogError("加载成功 " + m_path);
                            isLoading = false;
                        }
                        completeFunc.Invoke(o);
                    });
                }
                else
                {
                    Pop(completeFunc);
                }
            }

            private void LoadRes(Action completeFunc)
            {
                if (resLoader == null)
                {
                    resLoader = ResLoader.Alloc();
                }

                resLoader.Add2Load(m_path, (success, assetName, asset) =>
                {
                    m_Obj = asset as GameObject;
                    if (m_Obj != null)
                    {
                        m_OriginLayerMask = m_Obj.layer;
                        completeFunc.Invoke();
                    }
                    else
                    {
                        Debug.LogErrorFormat("加载资源：{0} 失败", assetName);
                    }
                });
                resLoader.Load();
            }

            // 从对象池弹出GameObject
            private void Pop(Action<GameObject> callback)
            {
                if (m_Obj)
                {
                    int count = m_Pool.Count;
                    if (count > 0)
                    {
                        GameObject go = m_Pool[count - 1];
                        m_Pool.RemoveAt(count - 1);
                        go.SetLayerRecursively(m_OriginLayerMask);
                        callback.Invoke(go);
                    }
                    else
                    {
                        GameObject go = GameObject.Instantiate(m_Obj);
                        callback.Invoke(go);
                    }
                }
                else
                {
                    LoadRes(() =>
                    {
                        GameObject go = GameObject.Instantiate(m_Obj);
                        callback.Invoke(go);
                    });
                }
            }

            // 将GameObject推入对象池
            private void Push(GameObject go)
            {
                if (m_Pool.Count >= m_Pool.Capacity)
                {
                    // 超过对象池上限的话，提前销毁
                    Destroy(go);
                }
                else
                {
                    go.SetLayerRecursively(CulledLayerMask);
                    go.transform.parent = ResObjectCacheManager.instance.transform;
                    m_Pool.Add(go);   
                }
            }

            public void UnLoad(GameObject go)
            {
                m_RefTimes -= 1;
                if (go)
                {
                    Push(go);
                }
            }

            public void Release()
            {
                foreach (var go in m_Pool)
                {
                    Destroy(go);
                }

                m_Pool = null;
                
                if (resLoader != null)
                {
                    resLoader.Recycle2Cache();
                    resLoader = null;
                }
                m_Obj = null;
            }
        }

        private Dictionary<string, ResCache> m_Caches;
        private static ResObjectCacheManager instance;

        private static ResObjectCacheManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = FindObjectOfType<ResObjectCacheManager>();
                }

                if (instance == null)
                {
                    instance = new GameObject("[ResObjectManager]").AddComponent<ResObjectCacheManager>();
                    DontDestroyOnLoad(instance.gameObject);
                }

                return instance;
            }
        }

        private float nextLoadTime = -1;
        void Update()
        {
            if (!isSlowLoading)
            {
                return;
            }
            if (nextLoadTime > 0 && Time.time < nextLoadTime)
            {
                return;
            }
            foreach (var pair in m_Caches)
            {
                if (pair.Value.canLoad)
                {
                    if (pair.Value.isLoading)
                    {
                        pair.Value.Load();
                        nextLoadTime = Time.time + 1;
                    }
                    break;
                }
            }
        }
        
        public void OnDestroy()
        {
            if (instance != null)
            {
                foreach (var pair in m_Caches)
                {
                    var cache = pair.Value;
                    cache.Release();
                }

                m_Caches.Clear();
            }

            instance = null;
        }

        public static void Dispose()
        {
            if (Instance)
            {
                GameObject.Destroy(Instance.gameObject);
            }
        }

        public static void Load(string path, Action<GameObject> completeFunc)
        {
            if (Instance.m_Caches == null)
                Instance.m_Caches = new Dictionary<string, ResCache>();

            var caches = Instance.m_Caches;
            if (!caches.ContainsKey(path))
                caches[path] = new ResCache(path);

            caches[path].Load(completeFunc);
        }

        public static void UnLoad(string path, GameObject go)
        {
            if (instance == null)
                return;
            if (instance.m_Caches == null)
                return;
            if (!instance.m_Caches.ContainsKey(path))
                return;

            instance.m_Caches[path].UnLoad(go);
            if (instance.m_Caches[path].RefTimes <= 0)
            {
                instance.m_Caches[path].Release();
                instance.m_Caches.Remove(path);
            }
        }

#if UNITY_EDITOR
        [BlackList]
        public List<string> GetAllResPath()
        {
            var paths = new List<string>();
            if (m_Caches == null)
                return paths;

            foreach (var pair in m_Caches)
                paths.Add(pair.Key);

            return paths;
        }

        [BlackList]
        public int GetResReference(string path)
        {
            if (m_Caches == null)
                return -2;

            if (!m_Caches.TryGetValue(path, out var cache))
                return -1;

            return cache.RefTimes;
        }

        [BlackList]
        public int GetResLoadTimes(string path)
        {
            if (m_Caches == null)
                return -2;

            if (!m_Caches.TryGetValue(path, out var cache))
                return -1;

            return cache.LoadTimes;
        }
#endif
    }
}
